﻿

#region Using Statements

using UnityEngine;
using System.Collections.Generic;

#endregion


/// <summary>
/// 
/// </summary>
public class Follow : MonoBehaviour
{

    #region Members

    /// <summary>
    /// Target to follow.
    /// </summary>
    public Transform target;


    /// <summary>
    /// Percentage of distance to target covered each second.
    /// </summary>
    public float exponentialApproachRate = 0.8f;


    /// <summary>
    /// Rate in world units that the object travels towards the target.
    /// </summary>
    public float linearApproachRate = 0.0f;


    /// <summary>
    /// Spring force pulling the object to the target.
    /// </summary>
    public float springForce = 0.0f;


    /// <summary>
    /// Spring force exponent (exponent applied to distance between object and target.)
    /// </summary>
    public float springExponent = 1.0f;


    /// <summary>
    /// Velocity damping for spring based motion.
    /// </summary>
    public float springDamping = 0.75f;
    private Vector3 springVelocity = Vector3.zero;


    /// <summary>
    /// Global position offset from target to follow.
    /// </summary>
    public Vector3 globalOffset = Vector3.up * 10.0f + Vector3.left * 5.0f + Vector3.back * 5.0f;


    /// <summary>
    /// Position offset relative to target's rotation.
    /// </summary>
    public Vector3 localOffset = Vector3.zero;


    /// <summary>
    /// Whether or not to update in fixed update mode.
    /// </summary>
    public bool useFixedUpdate = false;

    #endregion


    #region Init

	/// <summary>
    /// 
    /// </summary>
    void Awake()
    {

    }


    /// <summary>
    /// 
    /// </summary>
    void Start()
    {

    }

    #endregion


    #region Update

    /// <summary>
    /// 
    /// </summary>
    void Update()
    {
        if (target == null) return;

        if (!useFixedUpdate)
            SharedUpdate(Time.deltaTime);
    }


    /// <summary>
    /// 
    /// </summary>
    void FixedUpdate()
    {
        if (target == null) return;

        if (useFixedUpdate)
            SharedUpdate(Time.fixedDeltaTime);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="step"></param>
    private void SharedUpdate(float step)
    {
        // calculate target location
        Vector3 t = target.Offset(localOffset, globalOffset);

        // do linear motion
        Vector3 diff = t - transform.position;
        Vector3 dir = diff.normalized;
        float dist = diff.magnitude;
        float speed = Mathf.Min(linearApproachRate * step, dist);
        transform.position += dir * speed;

        // do exponential motion
        float ex = 1.0f - Mathf.Pow(1.0f - exponentialApproachRate, step);
        transform.position = Vector3.Lerp(transform.position, t, ex);

        // do spring motion
        springVelocity += dir * Mathf.Pow(dist, springExponent) * springForce * Time.deltaTime;
        springVelocity *= Util.DampingCoefficient(springDamping);
        transform.position += springVelocity * Time.deltaTime;
    }

    #endregion

}